// Copyright Leonid Shlyapnikov 2011
// Distributed under the GNU Lesser General Public License http://www.gnu.org/licenses/lgpl.html
//    author: Leonid Shlyapnikov, created on: Jun 13, 2011

#define BOOST_ENABLE_ASSERT_HANDLER
#include <boost/assert.hpp>
#include <boost/test/test_tools.hpp>
#include <boost/cstdint.hpp>
#include <boost/mpl/list.hpp>
#include <boost/foreach.hpp>
#include <sstream>
#include <exception>
#include <vector>
#include <string>

#include "../../main/fixparser/define_msg_macro.h"
#include "../../main/fixparser/FixMessage.h"
#include "../../main/fixparser/MemoryArea.h"
#include "../../main/fixparser/String.h"
#include "../../main/fixparser/FieldWriterVisitor.h"
#include "../../main/fixparser/util.h"
#include "../../main/fixparser/log.h"

using fixparser::MemoryArea;
using fixparser::FixMessage;
using fixparser::Field;
using fixparser::FieldList;
using fixparser::newString;
using fixparser::String;
using fixparser::static_strlen;


FIXPARSER_DEFINE_MSG(TestMsgDef, "Test", (8)(9)(35)(100)(101)(102)(103)(104));

FIXPARSER_DEFINE_MSG(TestMsgDef2, "Test2", (8)(9)(35)(49)(56)(34));

FIXPARSER_DEFINE_MSG(MsgTypeD, "NewOrderSingle", (8)(9)(35)(49)(56)(34)(11)(78));

struct FieldListerVisitor {
    std::vector<std::string> tagIds;

    void visit(const Field* field) {
        tagIds.push_back(field->tag().c_str());
    }
};

namespace boost
{
    void assertion_failed(char const * expr, char const * function, char const * file, int64_t line) {
        std::stringstream error;
        error << file << ":" << line << ": BOOST_ASSERT failed, expr: " << expr << ", function: " << function;
        BOOST_THROW_EXCEPTION(std::runtime_error(error.str()));
    }
}

void FixMessage_isFieldIndexed() {
    // GIVEN
    MemoryArea ma(1024);
    typedef fixparser::FixMessage<TestMsgDef> TestFixMsg;

    const size_t numberOfIndexedTags = TestMsgDef::numberOfIndexedTags();
    BOOST_CHECK_EQUAL(numberOfIndexedTags, 8);

    // WHEN
    TestFixMsg* msg = ma.create<TestFixMsg>();
    const size_t maSize = ma.size();

    // THEN
    BOOST_CHECK_EQUAL(0, msg->value(10).length());
    BOOST_CHECK_EQUAL(0, reinterpret_cast<int64_t>(msg->value(10).c_str()));

    BOOST_CHECK_EQUAL(false, msg->isFieldIndexed(100));
    BOOST_CHECK_THROW(msg->value(100), std::invalid_argument);
    BOOST_CHECK_THROW(msg->field(100), std::invalid_argument);

    BOOST_CHECK_EQUAL(false, msg->isFieldIndexed(101));
    BOOST_CHECK_THROW(msg->value(101), std::invalid_argument);
    BOOST_CHECK_THROW(msg->field(101), std::invalid_argument);

    BOOST_CHECK_EQUAL(false, msg->isFieldIndexed(103));
    BOOST_CHECK_THROW(msg->value(103), std::invalid_argument);
    BOOST_CHECK_THROW(msg->field(103), std::invalid_argument);

    BOOST_CHECK_EQUAL(false, msg->isFieldIndexed(104));
    BOOST_CHECK_THROW(msg->value(104), std::invalid_argument);
    BOOST_CHECK_THROW(msg->field(104), std::invalid_argument);

    BOOST_CHECK_EQUAL(ma.size(), maSize); //  memoryArea size did not change, nothing new allocated
}

void FixMessage_8_9_35_PredefinedOrder() {
    // GIVEN
    using fixparser::String;

    MemoryArea ma(1024);
    typedef fixparser::FixMessage<TestMsgDef> TestFixMsg;

    // WHEN
    TestFixMsg* msg = ma.create<TestFixMsg>();

    // THEN
    BOOST_CHECK_EQUAL(true, msg->isFieldIndexed(8));
    BOOST_CHECK_EQUAL(true, msg->isFieldIndexed(9));
    BOOST_CHECK_EQUAL(true, msg->isFieldIndexed(35));

    FieldListerVisitor visitor;
    msg->acceptVisitor(&visitor);

    BOOST_CHECK_EQUAL(visitor.tagIds[0].c_str(), "8");
    BOOST_CHECK_EQUAL(visitor.tagIds[1].c_str(), "9");
    BOOST_CHECK_EQUAL(visitor.tagIds[2].c_str(), "35");
}

void FixMessage_createEmptyMessage() {
    // GIVEN
    MemoryArea ma(1024);

    typedef fixparser::FixMessage<TestMsgDef> TestFixMsg;
    const size_t numberOfCachedFields = TestMsgDef::numberOfIndexedTags();
    BOOST_CHECK_EQUAL(numberOfCachedFields, 8);

    // WHEN
    const TestFixMsg* emptyMsg = ma.create<TestFixMsg>();

    // THEN
    BOOST_CHECK_NE(0, reinterpret_cast<int64_t>(emptyMsg));
    // size of message and size of checksum tag ID
    BOOST_CHECK(ma.size() > 0);
}

void FixMessage_createMessage() {
    // GIVEN
    MemoryArea ma(1024);

    typedef fixparser::FixMessage<TestMsgDef> TestFixMsg;
    const size_t numberOfCachedFields = TestMsgDef::numberOfIndexedTags();
    BOOST_CHECK_EQUAL(numberOfCachedFields, 8);

    TestFixMsg* msg = ma.create<TestFixMsg>();
    BOOST_CHECK_EQUAL(msg->numberOfFields(), 4); // 8, 9, 35 and 10 are always created

    size_t expectedMaSize = ma.size();

    {
        // GIVEN
        BOOST_MESSAGE("1). ma.size(): " << ma.size());

        // WHEN
        msg->setField(8, "FIX.4.2");

        // THEN
        expectedMaSize += static_strlen("FIX.4.2") + 1; // 8, 9 and 35 are always preallocated
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);
        BOOST_CHECK_EQUAL(msg->numberOfFields(), 4);
    }
    {
        // GIVEN
        BOOST_MESSAGE("2). ma.size(): " << ma.size());

        // WHEN
        const String* strD = newString(&ma, "D");
        expectedMaSize += sizeof(String) + static_strlen("D") + 1;
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);

        BOOST_MESSAGE("3). ma.size(): " << ma.size());
        BOOST_MESSAGE("    sizeof(Field): " << sizeof(Field));
        BOOST_MESSAGE("    sizeof(String): " << sizeof(String));

        // THEN
        msg->setField(35, *strD);
        // String is allocated as part of the Field, 35 is be copied into it
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);

        BOOST_CHECK_EQUAL(msg->numberOfFields(), 4);
    }
    {
        // GIVEN

        const char val0[] = "field100Value";
        const char val1[] = "newField100Value";

        const String* field100Value = newString(&ma, val0);
        const String* newField100Value = newString(&ma, val1);
        expectedMaSize += sizeof(String) + static_strlen(val0) + 1
                + sizeof(String) + static_strlen(val1) + 1;
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);

        // WHEN 1
        BOOST_CHECK_EQUAL(false, msg->isFieldIndexed(100));
        msg->setField(100, *field100Value);

        // THEN 1
        expectedMaSize += sizeof(Field) + static_strlen("100") + 1;  // only new field is allocated + tag string
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);
        BOOST_CHECK_EQUAL(msg->numberOfFields(), 5);
        BOOST_CHECK_EQUAL(val0, msg->value(100).c_str());

        // WHEN 2
        msg->setField(100, *newField100Value); // field already exists

        // THEN 2
        // size did not change: field, tag string and value string are already allocated
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);
        BOOST_CHECK_EQUAL(msg->numberOfFields(), 5);
        BOOST_CHECK_EQUAL(val1, msg->value(100).c_str());
        BOOST_CHECK_EQUAL(true, msg->isFieldIndexed(100));

        // WHEN 3
        msg->setField(100, *field100Value); // field already exists

        // THEN 3
        // size did not change: field, tag string and value string are already allocated
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);
        BOOST_CHECK_EQUAL(msg->numberOfFields(), 5);
        BOOST_CHECK_EQUAL(val0, msg->value(100).c_str());
    }
}

void FixMessage_writeTo() {
    // GIVEN
    MemoryArea ma(1024);
    typedef fixparser::FixMessage<TestMsgDef> TestFixMsg;
    TestFixMsg* msg = ma.create<TestFixMsg>();

    msg->setField(8, "FIX.4.2");
    msg->setField(9, "99");

    {
        const String* strD = newString(&ma, "D");
        msg->setField(35, *strD);
    }
    {
        const String* field100Value = newString(&ma, "field100Value");
        msg->setField(100, *field100Value);
    }
    {
        const String* field101Value = newString(&ma, "field101Value");
        msg->setField(101, *field101Value);
    }
    {
        const String* checksumVal = newString(&ma, "123");
        msg->setField(10, *checksumVal);
    }

    // WHEN
    std::stringstream ss;
    fixparser::FieldWriterVisitor<std::stringstream> fieldWriterVisitor(&ss);
    msg->acceptVisitor(&fieldWriterVisitor);

    // THEN
    std::stringstream expectedSs;
    expectedSs << "8=FIX.4.2";
    expectedSs.put(0x01);
    expectedSs << "9=99";
    expectedSs.put(0x01);
    expectedSs << "35=D";
    expectedSs.put(0x01);
    expectedSs << "100=field100Value";
    expectedSs.put(0x01);
    expectedSs << "101=field101Value";
    expectedSs.put(0x01);
    expectedSs << "10=123";
    expectedSs.put(0x01);
    LOG_INFO("ss: " << ss.str() << ", expected: "  << expectedSs.str());
    BOOST_CHECK_EQUAL(ss.str(), expectedSs.str());
    ma.reset(); // just to make sure that everything is OK, valgrind should not blow up
}

void FixMessage_setChecksum1() {
    // GIVEN

    MemoryArea ma(2048);
    typedef fixparser::FixMessage<TestMsgDef2> TestFixMsg2;

    // 8=FIX.4.2|9=65|35=A|49=SERVER|56=CLIENT|34=177|52=20090107-18:15:16|98=0|108=30|10=062|

    TestFixMsg2* msg = ma.create<TestFixMsg2>();
    msg->setField(8, newStringConst(&ma, "FIX.4.2")); // using fixparser::String
    msg->setField(9, std::string("65")); // using std::string
    msg->setField(35, "B"); // with B it should fail
    msg->setField(35, "A");
    msg->setField(49, "SERVER");
    msg->setField(56, "CLIENT");
    msg->setField(34, "177");
    msg->setField(52, "20090107-18:15:16");
    msg->setField(98, "0");
    msg->setField(108, "30");

    // WHEN
    BOOST_TEST_CHECKPOINT("WHEN");
    msg->setChecksum();

    // THEN
    BOOST_CHECK_EQUAL(msg->value(10).c_str(), "062");
}

void FixMessage_setChecksum2() {
    // GIVEN

    MemoryArea ma(2048);
    typedef fixparser::FixMessage<TestMsgDef2> TestFixMsg2;

    // 8=FIX.4.2|9=65|35=A|49=SERVER|56=CLIENT|34=177|52=20090107-18:15:16|98=0|108=30|10=062|

    TestFixMsg2* msg = ma.create<TestFixMsg2>();
    msg->setField(8, newStringConst(&ma, "FIX.4.2")); // using fixparser::String
    msg->setField(35, "A");
    msg->setField(49, "SERVER");
    msg->setField(56, "CLIENT");
    msg->setField(34, "177");
    msg->setField(52, "20090107-18:15:16");
    msg->setField(98, "0");
    msg->setField(108, "30");
    // will calculate and set BodyLength(9)
    msg->setBodyLength();

    const size_t constantNumberOfFields = msg->numberOfFields();

    // WHEN-1
    msg->setField(10, "123"); // which is wrong checksum
    // THEN-1
    BOOST_CHECK_EQUAL(msg->numberOfFields(), constantNumberOfFields);
    BOOST_CHECK_EQUAL(msg->value(10).c_str(), "123");

    // WHEN-2
    msg->setChecksum();
    // THEN-2
    BOOST_CHECK_EQUAL(msg->numberOfFields(), constantNumberOfFields);
    BOOST_CHECK_EQUAL(msg->value(10).c_str(), "062");
    BOOST_CHECK_EQUAL(msg->field(10)->value().c_str(), "062");
    // check that field and value point to the same string, the same address
    BOOST_CHECK_EQUAL( reinterpret_cast<int64_t>(msg->field(10)->value().c_str()),
                       reinterpret_cast<int64_t>(msg->value(10).c_str()) );
}

void FixMessage_setBodyLength1() {
    // GIVEN

    MemoryArea ma(2048);
    typedef fixparser::FixMessage<TestMsgDef2> TestFixMsg2;

    // 8=FIX.4.2|9=65|35=A|49=SERVER|56=CLIENT|34=177|52=20090107-18:15:16|98=0|108=30|10=062|

    TestFixMsg2* msg = ma.create<TestFixMsg2>();
    msg->setField(8, newStringConst(&ma, "FIX.4.2")); // using fixparser::String
    msg->setField(35, "A");
    msg->setField(49, "SERVER");
    msg->setField(56, "CLIENT");
    msg->setField(34, "177");
    msg->setField(52, "20090107-18:15:16");
    msg->setField(98, "0");
    msg->setField(108, "30");

    BOOST_CHECK_EQUAL(reinterpret_cast<int64_t>(msg->value(9).c_str()), 0);

    // WHEN
    BOOST_TEST_CHECKPOINT("WHEN");
    msg->setBodyLength();

    // THEN
    BOOST_CHECK_EQUAL(msg->value(9).c_str(), "65");
}

void FixMessage_setAll() {
    // GIVEN

    MemoryArea ma(2048);
    typedef fixparser::FixMessage<TestMsgDef2> TestFixMsg2;

    // 8=FIX.4.2|9=65|35=A|49=SERVER|56=CLIENT|34=177|52=20090107-18:15:16|98=0|108=30|10=062|

    TestFixMsg2* msg = ma.create<TestFixMsg2>();
    msg->setField(8, newStringConst(&ma, "FIX.4.2")); // using fixparser::String
    msg->setField(35, "B"); // with B it should fail
    msg->setField(35, "A");
    msg->setField(49, "SERVER");
    msg->setField(56, "CLIENT");
    msg->setField(34, "177");
    msg->setField(52, "20090107-18:15:16");
    msg->setField(98, "0");
    msg->setField(108, "30");

    // WHEN
    BOOST_TEST_CHECKPOINT("WHEN");
    msg->setAll();

    // THEN
    BOOST_CHECK_EQUAL(msg->value(10).c_str(), "062");
    BOOST_CHECK_EQUAL(msg->value(9).c_str(), "65");
}

void FixMessage_accessRepeatingGroup() {
    // GIVEN
    BOOST_TEST_CHECKPOINT("GIVEN");

    MemoryArea ma(2048);
    typedef fixparser::FixMessage<MsgTypeD> FixMessageTypeD;
    FixMessageTypeD* msg = ma.create<FixMessageTypeD>();

    BOOST_CHECK_EQUAL(msg->value(10).length(), 0);
    BOOST_CHECK_EQUAL(msg->value(9).length(), 0);

    msg->setField(8, "FIX.4.2");
    msg->setField(9, "99");
    msg->setField(35, "D");
    msg->setField(100, "field100Value");
    msg->setField(101, "field101Value");

    BOOST_CHECK_EQUAL(false, msg->isFieldIndexed(78));

    // repeating group
    msg->setField(78, "2");
    msg->setField(79, "AllocAccount1");
    msg->setField(80, "AllocShares1");
    msg->setField(79, "AllocAccount2");
    msg->setField(80, "AllocShares2");

    BOOST_CHECK_EQUAL(msg->value(9).c_str(), "99");
    msg->setAll();

    // THEN
    BOOST_TEST_CHECKPOINT("THEN");
    const fixparser::String& noAllocs = msg->value(78);
    BOOST_CHECK_EQUAL(noAllocs.c_str(), "2");

    const Field* currentField = msg->field(78);
    BOOST_CHECK_EQUAL(currentField->tag().c_str(),   "78");
    BOOST_CHECK_EQUAL(currentField->value().c_str(), "2");

    // an example of iterating over the tags

    currentField = currentField->next();
    BOOST_CHECK_EQUAL(currentField->tag().c_str(),   "79");
    BOOST_CHECK_EQUAL(currentField->value().c_str(), "AllocAccount1");

    currentField = currentField->next();
    BOOST_CHECK_EQUAL(currentField->tag().c_str(),   "80");
    BOOST_CHECK_EQUAL(currentField->value().c_str(), "AllocShares1");

    currentField = currentField->next();
    BOOST_CHECK_EQUAL(currentField->tag().c_str(),   "79");
    BOOST_CHECK_EQUAL(currentField->value().c_str(), "AllocAccount2");

    currentField = currentField->next();
    BOOST_CHECK_EQUAL(currentField->tag().c_str(),   "80");
    BOOST_CHECK_EQUAL(currentField->value().c_str(), "AllocShares2");
}
